use std::os::unix::prelude::PermissionsExt;

use chrono::{Local, Duration};
use diesel::prelude::*;
use md5::{Md5, Digest};
use crate::response;
use crate::model;

use crate::{models, ip_block::{self, IpBlock}, action, model::recovery_list_model::RecoveryList};

type DbError = Box<dyn std::error::Error + Send + Sync>;

/// Run query using Diesel to find user by uid and return it.
pub fn find_alert_by_id(
    alert_id: String,
    conn: &SqliteConnection,
) -> Result<Option<models::Alert>, DbError> {
    use crate::schema::alerts::dsl::*;

    let alert = alerts
        .filter(id.eq(alert_id))
        .first::<models::Alert>(conn)
        .optional()?;

    Ok(alert)
}

pub fn find_alert_by_page(
    page: i64,
    size: i64,
    source_op: Option<&String>,
    conn: &SqliteConnection,
) -> Result<(Option<Vec<models::Alert>>, i64), DbError> {
    use crate::schema::alerts::dsl::*;

    let offset = (page - 1) * size;

    match source_op {
        Some(src) => {
            let alert_vec = alerts
                //.filter(solved.eq(false))
                .filter(source.eq(src))
                .order(time.desc())
                .limit(size)
                .offset(offset)
                .load::<models::Alert>(conn)
                .optional()?;
    
            // let count = alerts.filter(solved.eq(false)).filter(source.eq(src)).count().get_result::<i64>(conn)?;
            let count = alerts.filter(source.eq(src)).count().get_result::<i64>(conn)?;
        
            Ok((alert_vec, count))
        },
        None => {
            let alert_vec = alerts
                //.filter(solved.eq(false))
                .order(time.desc())
                .limit(size)
                .offset(offset)
                .load::<models::Alert>(conn)
                .optional()?;
    
            // let count = alerts.filter(solved.eq(false)).count().get_result::<i64>(conn)?;
            let count = alerts.count().get_result::<i64>(conn)?;
        
            Ok((alert_vec, count))
        },
    }
    
}

/// Run query using Diesel to insert a new database row and return the result.
pub fn insert_new_alert(
    // prevent collision with `name` column imported inside the function
    new_alert: models::Alert,
    conn: &SqliteConnection,
) -> Result<models::Alert, DbError> {
    // It is common when using Diesel with Actix Web to import schema-related
    // modules inside a function's scope (rather than the normal module's scope)
    // to prevent import collisions and namespace pollution.
    use crate::schema::alerts::dsl::*;

    diesel::insert_into(alerts).values(&new_alert).execute(conn)?;

    Ok(new_alert)
}


/// Run query using Diesel to insert a new database row and return the result.
pub fn solve_alert(
    // prevent collision with `name` column imported inside the function
    alert_id: String,
    //ip_block: web::Data<ip_block::Iptables>,
    ip_block: &ip_block::Iptables,
    conn: &SqliteConnection,
) -> Result<bool, DbError> {
    // It is common when using Diesel with Actix Web to import schema-related
    // modules inside a function's scope (rather than the normal module's scope)
    // to prevent import collisions and namespace pollution.
    // use crate::schema::alerts::dsl::*;

    let alert_op = find_alert_by_id(alert_id, conn)?;

    solve_alert_op(&alert_op.unwrap(), "手动", ip_block, conn)
}


/// Run query using Diesel to insert a new database row and return the result.
pub fn solve_alert_op(
    alert: &models::Alert,
    source: &str,
    ip_block: &ip_block::Iptables,
    conn: &SqliteConnection,
) -> Result<bool, DbError> {
    
    // It is common when using Diesel with Actix Web to import schema-related
    // modules inside a function's scope (rather than the normal module's scope)
    // to prevent import collisions and namespace pollution.
    // use crate::schema::alerts::dsl::*;
    let mut hasher = Md5::new();
    hasher.update(alert.mal_obj_type.as_bytes());
    hasher.update(alert.mal_obj_key.as_bytes());
    let result = hasher.finalize();
    let id = format!("{:x}", result);
    let create_time = Local::now().timestamp_millis();
    //let expire_time = Local::now().checked_add_signed(Duration::days(365)).unwrap().timestamp_millis();
    let expire_time = Local::now().checked_add_signed(Duration::days(1)).unwrap().timestamp_millis();
    let mal_obj_key = alert.mal_obj_key.to_string();
    let mal_obj_type = alert.mal_obj_type.to_string();
    let recovery_type = alert.mal_obj_type.to_string();
    let mut recovery_key = alert.mal_obj_key.to_string();
    let mut recovery_value = "".to_string();

    // 阻断IP
    if alert.mal_obj_type == "IP" {
        // 执行IP阻断
        ip_block.add(&alert.mal_obj_key);
    }
    // 暂停进程
    if alert.mal_obj_type == "Process" {
        let pid = alert.mal_obj_key.parse::<i32>().unwrap();
        response::process_suspend::suspend(pid);
    }
    // 隔离文件
    if alert.mal_obj_type == "File" {
        // TODO 备份文件
        //&alert.mal_obj_key;
        // TODO 删除文件
        //&alert.mal_obj_key;
        recovery_key = "data/isolate/".to_string() + id.as_str();
        let perms = std::fs::metadata(alert.mal_obj_key.as_str()).unwrap().permissions();
        let mode = perms.mode();
        recovery_value = serde_json::to_string(&model::recovery_list_model::RecoveryValue  {
            mode: Some(mode)
        }).unwrap();
        response::file_isolate::isolate(alert.mal_obj_key.as_str(), &recovery_key);
    }

    // 记录处置记录
    let _result = action::recovery_list_action::insert(RecoveryList {
        id,
        mal_obj_key,
        mal_obj_type,
        recovery_type,
        recovery_key,
        recovery_value,
        create_time,
        expire_time,
        source: source.to_string()
    }, &conn);

    Ok(true)
}